﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;

namespace XmlDatabase.Core
{
    internal static class XElementExtensions
    {
        private static readonly List<String> generalTypes = new List<String>() {
      "System.Byte",
      "System.SByte",
      "System.Int16",
      "System.UInt16",
      "System.Int32",
      "System.UInt32",
      "System.Int64",
      "System.UInt64",
      "System.Double",
      "System.Decimal",
      "System.Single",
      "System.Boolean",
      "System.String",
      "System.DateTime"
    };
        public struct Entity
        {
            public Guid Id { get; set; }

            public Object Instance { get; set; }
        }
        public static XElementExtensions.Entity ConvertToObject<T>(this XElement e)
        {
            Guid id = new Guid();
            return new XElementExtensions.Entity()
            {
                Instance = XElementExtensions.GetTypeInstance(e, typeof(T), true, out id),
                Id = id
            };
        }
        public static XElement ConvertToXml(this Object instance, String elementName, Guid uuid)
        {
            return XElementExtensions.GetTypeElement(instance.GetType(), instance, elementName, uuid);
        }
        public static XElement ConvertToXml(this Object instance, Guid uuid)
        {
            return instance.ConvertToXml(instance.GetType().Name, uuid);
        }
        private static Object GetTypeInstance(XElement e, Type t, Boolean topLevel, out Guid id)
        {
            String fullName1 = t.FullName;
            Object instance1 = t.Assembly.CreateInstance(fullName1);
            foreach (PropertyInfo propertyInfo in t.GetProperties().Where<PropertyInfo>((_p) => _p.GetSetMethod() != null))
            {
                Type propertyType = propertyInfo.PropertyType;
                String fullName2 = propertyType.FullName;
                if (!XElementExtensions.generalTypes.Contains(fullName2))
                {
                    if (propertyType.IsEnum)
                    {
                        XElement xelement = e.Element(propertyInfo.Name);
                        if (xelement != null)
                        {
                            propertyInfo.SetValue(instance1, Enum.Parse(propertyInfo.PropertyType, xelement.Attribute("Enum").Value), null);
                        }
                        else if (propertyType.GetInterface(typeof(IList).FullName) != null && propertyType.IsGenericType)
                        {
                            IList instance2 = (IList)typeof(List<String>).Assembly.CreateInstance(propertyType.FullName);
                            Type genericArgument = propertyType.GetGenericArguments()[0];
                            foreach (XElement element in e.Element(propertyInfo.Name).Elements())
                            {
                                instance2.Add(XElementExtensions.GetTypeInstance(element, genericArgument, false, out id));
                            }
                            propertyInfo.SetValue(instance1, instance2, null);
                        }
                        else if (propertyType.IsClass || propertyType.IsValueType)
                        {
                            XElement e1 = e.Element(propertyInfo.Name);
                            if (e1 != null)
                            {
                                propertyInfo.SetValue(instance1, XElementExtensions.GetTypeInstance(e1, propertyType, false, out id), null);
                            }
                        }
                    }
                }
                else if (e.Attribute(propertyInfo.Name) != null)
                {
                    propertyInfo.SetValue(instance1, Convert.ChangeType(e.Attribute(propertyInfo.Name).Value, propertyType), null);
                }
            }
            id = !topLevel || e.Attribute("_uuid") == null ? Guid.Empty : new Guid(e.Attribute("_uuid").Value);
            return instance1;
        }
        private static XElement GetTypeElement(Type t, Object instance, String name, Guid uuid)
        {
            XElement xelement1 = new XElement(name);
            if (uuid != Guid.Empty)
            {
                xelement1.Add(new XAttribute("_uuid", uuid.ToString()));
            }
            foreach (PropertyInfo propertyInfo in t.GetProperties().Where<PropertyInfo>((_p) =>
            {
                if (_p.GetGetMethod() != null)
                {
                    return _p.GetValue(instance, null) != null;
                }
                return false;
            }))
            {
                Type propertyType = propertyInfo.PropertyType;
                String fullName = propertyType.FullName;
                if (!XElementExtensions.generalTypes.Contains(fullName))
                {
                    if (propertyType.IsEnum)
                    {
                        XElement xelement2 = new XElement(propertyInfo.Name);
                        xelement2.Add(new XAttribute("Enum", propertyInfo.GetValue(instance, null)));
                        xelement1.Add(xelement2);
                    }
                    else if (propertyType.GetInterface(typeof(IList).FullName) != null && propertyType.IsGenericType)
                    {
                        if (propertyInfo.GetValue(instance, null) is IList list && list.Count > 0)
                        {
                            XElement xelement2 = new XElement(propertyInfo.Name);
                            foreach (Object instance1 in list)
                            {
                                XElement typeElement = XElementExtensions.GetTypeElement(instance1.GetType(), instance1, propertyInfo.Name.Substring(0, propertyInfo.Name.Length - 1), Guid.Empty);
                                xelement2.Add(typeElement);
                            }
                            xelement1.Add(xelement2);
                        }
                    }
                    else if (propertyType.IsClass || propertyType.IsValueType)
                    {
                        Object instance1 = propertyInfo.GetValue(instance, null);
                        xelement1.Add(XElementExtensions.GetTypeElement(propertyType, instance1, propertyInfo.Name, Guid.Empty));
                    }
                }
                else
                {
                    xelement1.Add(new XAttribute(propertyInfo.Name, propertyInfo.GetValue(instance,null)));
                }
            }
            return xelement1;
        }
    }
}
